//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package project_issue_api_serv

import (
	"context"
	"time"

	"atomgit.com/openlinksaas/api-server/api_common"
	"atomgit.com/openlinksaas/api-server/dao"
	"atomgit.com/openlinksaas/api-server/dao/comment_dao"
	"atomgit.com/openlinksaas/api-server/dao/issue_dao"
	"atomgit.com/openlinksaas/api-server/dao/project_dao"
	"atomgit.com/openlinksaas/api-server/dao/project_entry_dao"
	"atomgit.com/openlinksaas/api-server/dao/requirement_dao"
	"atomgit.com/openlinksaas/api-server/dao/user_dao"
	"atomgit.com/openlinksaas/api-server/dao/watch_dao"
	"atomgit.com/openlinksaas/api-server/utils"
	"atomgit.com/openlinksaas/proto-gen-go.git/events/events_issue"
	"atomgit.com/openlinksaas/proto-gen-go.git/notices/notices_issue"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_comment_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_entry_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_issue_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_recycle_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/project_watch_api"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/mongo"
)

type ProjectIssueApiImpl struct {
	project_issue_api.UnimplementedProjectIssueApiServer
}

func (apiImpl *ProjectIssueApiImpl) Create(ctx context.Context, req *project_issue_api.CreateRequest) (*project_issue_api.CreateResponse, error) {
	if req.BasicInfo.TagIdList == nil {
		req.BasicInfo.TagIdList = []string{}
	}

	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.CreateResponse{
			Code:   project_issue_api.CreateResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, _, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.CreateResponse{
			Code:   project_issue_api.CreateResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.CreateResponse{
			Code:   project_issue_api.CreateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	wrongIssueType := false
	taskExtraInfo, taskOk := req.ExtraInfo.(*project_issue_api.CreateRequest_ExtraTaskInfo)
	bugExtraInfo, bugOk := req.ExtraInfo.(*project_issue_api.CreateRequest_ExtraBugInfo)
	if req.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK {
		if !taskOk {
			wrongIssueType = true
		}
	} else if req.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG {
		if !bugOk {
			wrongIssueType = true
		}
	} else {
		wrongIssueType = true
	}
	if wrongIssueType {
		return &project_issue_api.CreateResponse{
			Code:   project_issue_api.CreateResponse_CODE_WRONG_ISSUE_TYPE,
			ErrMsg: "错误的工单类型",
		}, nil
	}
	//检查标签
	var tagScopeType project_api.TAG_SCOPE_TYPE
	if req.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_TASK
	} else if req.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_BUG
	}
	tagItemList, err := project_dao.TagInfoDao.List(ctx, req.ProjectId, tagScopeType)
	if err != nil {
		return nil, err
	}
	tagItemMap := map[string]*project_dao.TagInfoDbItem{}
	for _, tagItem := range tagItemList {
		tagItemMap[tagItem.TagId] = tagItem
	}
	for _, tagId := range req.BasicInfo.TagIdList {
		_, ok = tagItemMap[tagId]
		if !ok {
			return &project_issue_api.CreateResponse{
				Code:   project_issue_api.CreateResponse_CODE_NO_TAG,
				ErrMsg: "标签不存在",
			}, nil
		}
	}

	issueId := uuid.NewString()
	timeNow := time.Now().UnixNano() / 1e6

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		issueIndex, err := project_dao.ProjectCounterDao.GetNextValue(sessCtx, req.ProjectId, project_dao.COUNTER_KEY_ISSUE_ID)
		if err != nil {
			return nil, err
		}
		dbItem := &issue_dao.IssueInfoDbItem{
			IssueId:    issueId,
			IssueType:  int32(req.IssueType),
			IssueIndex: int64(issueIndex),
			BasicInfo: issue_dao.BasicIssueInfoDbItem{
				Title:     req.BasicInfo.Title,
				Content:   req.BasicInfo.Content,
				TagIdList: req.BasicInfo.TagIdList,
			},
			ProjectId:    req.ProjectId,
			CreateTime:   timeNow,
			UpdateTime:   timeNow,
			State:        int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_PLAN),
			ProcessStage: int32(project_issue_api.PROCESS_STAGE_PROCESS_STAGE_TODO),
			CreateUserId: sessionItem.UserId,
		}

		if taskOk {
			dbItem.ExtraTaskInfo = &issue_dao.ExtraTaskInfoDbItem{
				Priority: int32(taskExtraInfo.ExtraTaskInfo.Priority),
			}
		}
		if bugOk {
			dbItem.ExtraBugInfo = &issue_dao.ExtraBugInfoDbItem{
				SoftwareVersion: bugExtraInfo.ExtraBugInfo.SoftwareVersion,
				Level:           int32(bugExtraInfo.ExtraBugInfo.Level),
				Priority:        int32(bugExtraInfo.ExtraBugInfo.Priority),
			}
		}

		err = issue_dao.IssueDao.Insert(sessCtx, dbItem)
		if err != nil {
			return nil, err
		}
		err = project_dao.MemberStateDao.MarkIssueStateDirty(sessCtx, req.ProjectId, req.IssueType, []string{sessionItem.UserId})
		if err != nil {
			return nil, err
		}
		//添加关注
		targetType := uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK)
		if req.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG {
			targetType = uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG)
		}
		exist, err := watch_dao.MyWatchDao.Exist(sessCtx, req.ProjectId, sessionItem.UserId, issueId, targetType)
		if err != nil {
			return nil, err
		}
		if !exist {
			err = watch_dao.MyWatchDao.Insert(sessCtx, &watch_dao.MyWatchInfoDbItem{
				ProjectId:  req.ProjectId,
				UserId:     sessionItem.UserId,
				TargetId:   issueId,
				TargetType: targetType,
				WatchTime:  timeNow,
			})
			if err != nil {
				return nil, err
			}
		}

		return &project_issue_api.CreateResponse{
			Code:    project_issue_api.CreateResponse_CODE_OK,
			IssueId: issueId,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	//记录事件
	go apiImpl.addCreateEvent(sessionItem, req, projectItem, issueId, timeNow)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.NewIssueNotice{
		ProjectId:    req.ProjectId,
		IssueId:      issueId,
		CreateUserId: sessionItem.UserId,
	})

	return ret.(*project_issue_api.CreateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) checkUpdate(ctx context.Context, sessionId, projectId, issueId string) (*dao.SessionDbItem, *project_dao.ProjectInfoDbItem, *issue_dao.IssueInfoDbItem, *project_issue_api.UpdateResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, sessionId)
	if !ok {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, projectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, nil, nil, nil, err
	}
	issueItem, err := issue_dao.IssueDao.Get(ctx, projectId, issueId)
	if err != nil {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}
	if issueItem.State == int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_CLOSE) {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	if !(sessionItem.UserId == projectItem.OwnerUserId || roleItem.BasicInfo.Admin || issueItem.CreateUserId == sessionItem.UserId) {
		return nil, nil, nil, &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	return sessionItem, projectItem, issueItem, nil, nil
}

func (apiImpl *ProjectIssueApiImpl) checkUpdateExtra(issueItem *issue_dao.IssueInfoDbItem, extraInfo interface{}) (*project_issue_api.UpdateResponse, error) {
	wrongIssueType := false
	if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
		_, taskOk := extraInfo.(*project_issue_api.UpdateRequest_ExtraTaskInfo)
		_, taskOk2 := extraInfo.(*project_issue_api.UpdateExtraInfoRequest_ExtraTaskInfo)
		if !(taskOk || taskOk2) {
			wrongIssueType = true
		}
	} else if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
		_, bugOk := extraInfo.(*project_issue_api.UpdateRequest_ExtraBugInfo)
		_, bugOk2 := extraInfo.(*project_issue_api.UpdateExtraInfoRequest_ExtraBugInfo)
		if !(bugOk || bugOk2) {
			wrongIssueType = true
		}
	} else {
		wrongIssueType = true
	}
	if wrongIssueType {
		return &project_issue_api.UpdateResponse{
			Code:   project_issue_api.UpdateResponse_CODE_WRONG_ISSUE_TYPE,
			ErrMsg: "错误的工单类型",
		}, nil
	}
	return nil, nil
}

func (apiImpl *ProjectIssueApiImpl) afterUpdate(ctx context.Context, sessionItem *dao.SessionDbItem, projectItem *project_dao.ProjectInfoDbItem, issueItem *issue_dao.IssueInfoDbItem, newTitle string) {
	go apiImpl.addUpdateEvent(sessionItem, projectItem, issueItem, newTitle)
	go api_common.SendNoticeToProjectScope(context.Background(), projectItem.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: projectItem.ProjectId,
		IssueId:   issueItem.IssueId,
	})
	if newTitle != "" {
		issueItem.BasicInfo.Title = newTitle
	}
}

func (apiImpl *ProjectIssueApiImpl) Update(ctx context.Context, req *project_issue_api.UpdateRequest) (*project_issue_api.UpdateResponse, error) {
	if req.BasicInfo.TagIdList == nil {
		req.BasicInfo.TagIdList = []string{}
	}
	sessionItem, projectItem, issueItem, response, err := apiImpl.checkUpdate(ctx, req.SessionId, req.ProjectId, req.IssueId)
	if response != nil || err != nil {
		return response, err
	}
	response, err = apiImpl.checkUpdateExtra(issueItem, req.ExtraInfo)
	if response != nil || err != nil {
		return response, err
	}
	noChange := true
	if issueItem.BasicInfo.Title != req.BasicInfo.Title || issueItem.BasicInfo.Content != req.BasicInfo.Content {
		noChange = false
	}
	if len(issueItem.BasicInfo.TagIdList) != len(req.BasicInfo.TagIdList) {
		noChange = false
	} else {
		for index, tagId := range req.BasicInfo.TagIdList {
			if issueItem.BasicInfo.TagIdList[index] != tagId {
				noChange = false
			}
		}
	}
	if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) && req.ExtraInfo != nil && issueItem.ExtraBugInfo != nil {
		extraInfo := req.ExtraInfo.(*project_issue_api.UpdateRequest_ExtraBugInfo)
		if extraInfo.ExtraBugInfo.Level != project_issue_api.BUG_LEVEL(issueItem.ExtraBugInfo.Level) ||
			extraInfo.ExtraBugInfo.Priority != project_issue_api.BUG_PRIORITY(issueItem.ExtraBugInfo.Priority) ||
			extraInfo.ExtraBugInfo.SoftwareVersion != issueItem.ExtraBugInfo.SoftwareVersion {
			noChange = false
		}
	} else if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) && req.ExtraInfo != nil && issueItem.ExtraTaskInfo != nil {
		extraInfo := req.ExtraInfo.(*project_issue_api.UpdateRequest_ExtraTaskInfo)
		if extraInfo.ExtraTaskInfo.Priority != project_issue_api.TASK_PRIORITY(issueItem.ExtraTaskInfo.Priority) {
			noChange = false
		}
	}
	if noChange {
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	}
	//检查标签
	var tagScopeType project_api.TAG_SCOPE_TYPE
	if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_TASK
	} else if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_BUG
	}
	tagItemList, err := project_dao.TagInfoDao.List(ctx, req.ProjectId, tagScopeType)
	if err != nil {
		return nil, err
	}
	tagItemMap := map[string]*project_dao.TagInfoDbItem{}
	for _, tagItem := range tagItemList {
		tagItemMap[tagItem.TagId] = tagItem
	}
	for _, tagId := range req.BasicInfo.TagIdList {
		_, ok := tagItemMap[tagId]
		if !ok {
			return &project_issue_api.UpdateResponse{
				Code:   project_issue_api.UpdateResponse_CODE_NO_TAG,
				ErrMsg: "标签不存在",
			}, nil
		}
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		var extraTaskItem *issue_dao.ExtraTaskInfoDbItem
		var extraBugItem *issue_dao.ExtraBugInfoDbItem
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
			extraTaskItem = &issue_dao.ExtraTaskInfoDbItem{
				Priority: int32(req.ExtraInfo.(*project_issue_api.UpdateRequest_ExtraTaskInfo).ExtraTaskInfo.Priority),
			}
		}
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			bugExtraInfo := req.ExtraInfo.(*project_issue_api.UpdateRequest_ExtraBugInfo)
			extraBugItem = &issue_dao.ExtraBugInfoDbItem{
				SoftwareVersion: bugExtraInfo.ExtraBugInfo.SoftwareVersion,
				Level:           int32(bugExtraInfo.ExtraBugInfo.Level),
				Priority:        int32(bugExtraInfo.ExtraBugInfo.Priority),
			}
		}
		err := issue_dao.IssueDao.Update(sessCtx, req.ProjectId, req.IssueId, &issue_dao.BasicIssueInfoDbItem{
			Title:   req.BasicInfo.Title,
			Content: req.BasicInfo.Content,
		}, extraTaskItem, extraBugItem)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	apiImpl.afterUpdate(ctx, sessionItem, projectItem, issueItem, req.BasicInfo.Title)
	return ret.(*project_issue_api.UpdateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) UpdateTitle(ctx context.Context, req *project_issue_api.UpdateTitleRequest) (*project_issue_api.UpdateResponse, error) {
	sessionItem, projectItem, issueItem, response, err := apiImpl.checkUpdate(ctx, req.SessionId, req.ProjectId, req.IssueId)
	if response != nil || err != nil {
		return response, err
	}
	if req.Title == issueItem.BasicInfo.Title {
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.UpdateTitle(sessCtx, req.ProjectId, req.IssueId, req.Title)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	apiImpl.afterUpdate(ctx, sessionItem, projectItem, issueItem, req.Title)
	return ret.(*project_issue_api.UpdateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) UpdateTagIdList(ctx context.Context, req *project_issue_api.UpdateTagIdListRequest) (*project_issue_api.UpdateResponse, error) {
	sessionItem, projectItem, issueItem, response, err := apiImpl.checkUpdate(ctx, req.SessionId, req.ProjectId, req.IssueId)
	if response != nil || err != nil {
		return response, err
	}

	//检查标签
	var tagScopeType project_api.TAG_SCOPE_TYPE
	if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_TASK
	} else if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
		tagScopeType = project_api.TAG_SCOPE_TYPE_TAG_SCOPE_BUG
	}
	tagItemList, err := project_dao.TagInfoDao.List(ctx, req.ProjectId, tagScopeType)
	if err != nil {
		return nil, err
	}
	tagItemMap := map[string]*project_dao.TagInfoDbItem{}
	for _, tagItem := range tagItemList {
		tagItemMap[tagItem.TagId] = tagItem
	}
	for _, tagId := range req.TagIdList {
		_, ok := tagItemMap[tagId]
		if !ok {
			return &project_issue_api.UpdateResponse{
				Code:   project_issue_api.UpdateResponse_CODE_NO_TAG,
				ErrMsg: "标签不存在",
			}, nil
		}
	}

	oldTagList := []*events_issue.TagInfo{}
	newTagList := []*events_issue.TagInfo{}
	for _, tagId := range issueItem.BasicInfo.TagIdList {
		tagItem, ok := tagItemMap[tagId]
		if ok {
			oldTagList = append(oldTagList, &events_issue.TagInfo{
				TagId:   tagId,
				TagName: tagItem.TagName,
			})
		}
	}
	for _, tagId := range req.TagIdList {
		tagItem, ok := tagItemMap[tagId]
		if ok {
			newTagList = append(newTagList, &events_issue.TagInfo{
				TagId:   tagId,
				TagName: tagItem.TagName,
			})
		}
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.UpdateTagIdList(sessCtx, req.ProjectId, req.IssueId, req.TagIdList)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	//记录事件
	go apiImpl.addUpdateTagEvent(sessionItem, projectItem, issueItem, oldTagList, newTagList)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), projectItem.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: projectItem.ProjectId,
		IssueId:   issueItem.IssueId,
	})
	return ret.(*project_issue_api.UpdateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) UpdateContent(ctx context.Context, req *project_issue_api.UpdateContentRequest) (*project_issue_api.UpdateResponse, error) {
	sessionItem, projectItem, issueItem, response, err := apiImpl.checkUpdate(ctx, req.SessionId, req.ProjectId, req.IssueId)
	if response != nil || err != nil {
		return response, err
	}
	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.UpdateContent(sessCtx, req.ProjectId, req.IssueId, req.Content)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	apiImpl.afterUpdate(ctx, sessionItem, projectItem, issueItem, issueItem.BasicInfo.Title)
	return ret.(*project_issue_api.UpdateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) UpdateExtraInfo(ctx context.Context, req *project_issue_api.UpdateExtraInfoRequest) (*project_issue_api.UpdateResponse, error) {
	sessionItem, projectItem, issueItem, response, err := apiImpl.checkUpdate(ctx, req.SessionId, req.ProjectId, req.IssueId)
	if response != nil || err != nil {
		return response, err
	}
	response, err = apiImpl.checkUpdateExtra(issueItem, req.ExtraInfo)
	if response != nil || err != nil {
		return response, err
	}
	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		var extraTaskItem *issue_dao.ExtraTaskInfoDbItem
		var extraBugItem *issue_dao.ExtraBugInfoDbItem
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
			extraTaskItem = &issue_dao.ExtraTaskInfoDbItem{
				Priority: int32(req.ExtraInfo.(*project_issue_api.UpdateExtraInfoRequest_ExtraTaskInfo).ExtraTaskInfo.Priority),
			}
		}
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			bugExtraInfo := req.ExtraInfo.(*project_issue_api.UpdateExtraInfoRequest_ExtraBugInfo)
			extraBugItem = &issue_dao.ExtraBugInfoDbItem{
				SoftwareVersion: bugExtraInfo.ExtraBugInfo.SoftwareVersion,
				Level:           int32(bugExtraInfo.ExtraBugInfo.Level),
				Priority:        int32(bugExtraInfo.ExtraBugInfo.Priority),
			}
		}
		err = issue_dao.IssueDao.UpdateExtraInfo(sessCtx, req.ProjectId, req.IssueId, extraTaskItem, extraBugItem)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateResponse{
			Code: project_issue_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	apiImpl.afterUpdate(ctx, sessionItem, projectItem, issueItem, issueItem.BasicInfo.Title)
	return ret.(*project_issue_api.UpdateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) AssignExecUser(ctx context.Context, req *project_issue_api.AssignExecUserRequest) (*project_issue_api.AssignExecUserResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	exist, err := project_dao.MemberInfoDao.Exist(ctx, req.ProjectId, req.ExecUserId)
	if err != nil {
		return nil, err
	}
	if !exist {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NO_EXEC_USER,
			ErrMsg: "执行用户不存在",
		}, nil
	}

	//只有在规划和处理阶段可以更换执行人
	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}
	if issueItem.ExecUserId == req.ExecUserId {
		return &project_issue_api.AssignExecUserResponse{
			Code: project_issue_api.AssignExecUserResponse_CODE_OK,
		}, nil
	}
	if req.ExecUserId == issueItem.CheckUserId {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NOT_ALLOW,
			ErrMsg: "执行和检查者不能是同一人",
		}, nil
	}
	userPerm := apiImpl.calcUserPerm(sessionItem, projectItem, roleItem, issueItem, nil)
	if !userPerm.CanAssignExecUser {
		return &project_issue_api.AssignExecUserResponse{
			Code:   project_issue_api.AssignExecUserResponse_CODE_NOT_ALLOW,
			ErrMsg: "当前状态不能变更执行者",
		}, nil
	}

	nowTime := time.Now().UnixNano() / 1e6
	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.AssignExecUser(sessCtx, req.ProjectId, req.IssueId, req.ExecUserId,
			int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_PROCESS), int32(project_issue_api.PROCESS_STAGE_PROCESS_STAGE_TODO), nowTime)
		if err != nil {
			return nil, err
		}
		dirtyUserIdList := []string{issueItem.CreateUserId, req.ExecUserId}
		if issueItem.ExecUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.ExecUserId)
		}
		if issueItem.CheckUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.CheckUserId)
		}

		err = project_dao.MemberStateDao.MarkIssueStateDirty(sessCtx, req.ProjectId, project_issue_api.ISSUE_TYPE(issueItem.IssueType), dirtyUserIdList)
		if err != nil {
			return nil, err
		}

		//添加关注
		targetType := uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK)
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			targetType = uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG)
		}
		exist, err = watch_dao.MyWatchDao.Exist(sessCtx, req.ProjectId, req.ExecUserId, req.IssueId, targetType)
		if err != nil {
			return nil, err
		}
		if !exist {
			err = watch_dao.MyWatchDao.Insert(sessCtx, &watch_dao.MyWatchInfoDbItem{
				ProjectId:  req.ProjectId,
				UserId:     req.ExecUserId,
				TargetId:   req.IssueId,
				TargetType: targetType,
				WatchTime:  nowTime,
			})
			if err != nil {
				return nil, err
			}
		}

		return &project_issue_api.AssignExecUserResponse{
			Code: project_issue_api.AssignExecUserResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	userItem, err := user_dao.UserInfoDao.Get(ctx, req.ExecUserId)
	if err != nil {
		return nil, err
	}
	//记录事件
	go apiImpl.addAssignExecUserEvent(sessionItem, req, projectItem, issueItem, userItem)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: req.ProjectId,
		IssueId:   req.IssueId,
	})
	return ret.(*project_issue_api.AssignExecUserResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) AssignCheckUser(ctx context.Context, req *project_issue_api.AssignCheckUserRequest) (*project_issue_api.AssignCheckUserResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	exist, err := project_dao.MemberInfoDao.Exist(ctx, req.ProjectId, req.CheckUserId)
	if err != nil {
		return nil, err
	}
	if !exist {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NO_CHECK_USER,
			ErrMsg: "检查用户不存在",
		}, nil
	}
	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}
	if issueItem.CheckUserId == req.CheckUserId {
		return &project_issue_api.AssignCheckUserResponse{
			Code: project_issue_api.AssignCheckUserResponse_CODE_OK,
		}, nil
	}
	if req.CheckUserId == issueItem.ExecUserId {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NOT_ALLOW,
			ErrMsg: "执行和检查者不能是同一人",
		}, nil
	}
	userPerm := apiImpl.calcUserPerm(sessionItem, projectItem, roleItem, issueItem, nil)
	if !userPerm.CanAssignCheckUser {
		return &project_issue_api.AssignCheckUserResponse{
			Code:   project_issue_api.AssignCheckUserResponse_CODE_NOT_ALLOW,
			ErrMsg: "当前状态不能变更检查者",
		}, nil
	}

	nowTime := time.Now().UnixNano() / 1e6
	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.AssignCheckUser(sessCtx, req.ProjectId, req.IssueId, req.CheckUserId, nowTime)
		if err != nil {
			return nil, err
		}
		dirtyUserIdList := []string{issueItem.CreateUserId, req.CheckUserId}
		if issueItem.ExecUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.ExecUserId)
		}
		if issueItem.CheckUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.CheckUserId)
		}

		err = project_dao.MemberStateDao.MarkIssueStateDirty(sessCtx, req.ProjectId, project_issue_api.ISSUE_TYPE(issueItem.IssueType), dirtyUserIdList)
		if err != nil {
			return nil, err
		}

		//添加关注
		targetType := uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK)
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			targetType = uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG)
		}
		exist, err = watch_dao.MyWatchDao.Exist(sessCtx, req.ProjectId, req.CheckUserId, req.IssueId, targetType)
		if err != nil {
			return nil, err
		}
		if !exist {
			err = watch_dao.MyWatchDao.Insert(sessCtx, &watch_dao.MyWatchInfoDbItem{
				ProjectId:  req.ProjectId,
				UserId:     req.CheckUserId,
				TargetId:   req.IssueId,
				TargetType: targetType,
				WatchTime:  nowTime,
			})
			if err != nil {
				return nil, err
			}
		}

		return &project_issue_api.AssignCheckUserResponse{
			Code: project_issue_api.AssignCheckUserResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	userItem, err := user_dao.UserInfoDao.Get(ctx, req.CheckUserId)
	if err != nil {
		return nil, err
	}
	//记录事件
	apiImpl.addAssignCheckUser(sessionItem, req, projectItem, issueItem, userItem)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: req.ProjectId,
		IssueId:   req.IssueId,
	})
	return ret.(*project_issue_api.AssignCheckUserResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) ChangeState(ctx context.Context, req *project_issue_api.ChangeStateRequest) (*project_issue_api.ChangeStateResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	if projectItem.Closed {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}
	if issueItem.State == int32(req.State) {
		return &project_issue_api.ChangeStateResponse{
			Code: project_issue_api.ChangeStateResponse_CODE_OK,
		}, nil
	}
	//获取子任务状态
	subIssueItemList, err := issue_dao.SubIssueDao.ListByIssue(ctx, req.ProjectId, []string{req.IssueId})
	if err != nil {
		return nil, err
	}
	subIssueStatus := &project_issue_api.SubIssueStatus{
		SubIssueList: []*project_issue_api.SimpleSubIssue{},
		TotalCount:   uint32(len(subIssueItemList)),
	}
	for _, subIssueItem := range subIssueItemList {
		if subIssueItem.Done {
			subIssueStatus.DoneCount += 1
		}
		subIssueStatus.SubIssueList = append(subIssueStatus.SubIssueList, subIssueItem.ToSimpleSubIssue())
	}

	userPerm := apiImpl.calcUserPerm(sessionItem, projectItem, roleItem, issueItem, subIssueStatus)
	match := false
	for _, nextStat := range userPerm.NextStateList {
		if nextStat == req.State {
			match = true
			break
		}
	}
	if !match {
		return &project_issue_api.ChangeStateResponse{
			Code:   project_issue_api.ChangeStateResponse_CODE_WRONG_STATE,
			ErrMsg: "不允许的操作",
		}, nil
	}

	nowTime := time.Now().UnixNano() / 1e6
	reOpen := issueItem.State == int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_CLOSE) && req.State != project_issue_api.ISSUE_STATE_ISSUE_STATE_CLOSE

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		if issueItem.HasRemainMinutes && issueItem.RemainMinutes > 0 && req.State == project_issue_api.ISSUE_STATE_ISSUE_STATE_CLOSE {
			err = issue_dao.IssueDao.SetRemainMinutes(sessCtx, req.ProjectId, req.IssueId, 0, nowTime)
			if err != nil {
				return nil, err
			}
		}
		if issueItem.State == int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_PLAN) && req.State == project_issue_api.ISSUE_STATE_ISSUE_STATE_PROCESS {
			err = issue_dao.IssueDao.AssignExecUser(sessCtx, req.ProjectId, req.IssueId, sessionItem.UserId,
				int32(req.State), int32(project_issue_api.PROCESS_STAGE_PROCESS_STAGE_TODO), nowTime)
			if err != nil {
				return nil, err
			}
		} else {
			err = issue_dao.IssueDao.UpdateState(sessCtx, req.ProjectId, req.IssueId, int32(req.State), int32(project_issue_api.PROCESS_STAGE_PROCESS_STAGE_TODO), nowTime)
			if err != nil {
				return nil, err
			}
		}
		if req.State == project_issue_api.ISSUE_STATE_ISSUE_STATE_CLOSE {
			//删除关注
			if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
				err = watch_dao.MyWatchDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK), req.IssueId)
				if err != nil {
					return nil, err
				}
				err = comment_dao.UnReadDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_comment_api.COMMENT_TARGET_TYPE_COMMENT_TARGET_TASK), req.IssueId)
				if err != nil {
					return nil, err
				}
			}
			if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
				err = watch_dao.MyWatchDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG), req.IssueId)
				if err != nil {
					return nil, err
				}
				err = comment_dao.UnReadDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_comment_api.COMMENT_TARGET_TYPE_COMMENT_TARGET_BUG), req.IssueId)
				if err != nil {
					return nil, err
				}
			}
		}
		dirtyUserIdList := []string{issueItem.CreateUserId}
		if issueItem.ExecUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.ExecUserId)
		}
		if issueItem.CheckUserId != "" {
			dirtyUserIdList = append(dirtyUserIdList, issueItem.CheckUserId)
		}

		err = project_dao.MemberStateDao.MarkIssueStateDirty(sessCtx, req.ProjectId, project_issue_api.ISSUE_TYPE(issueItem.IssueType), dirtyUserIdList)
		if err != nil {
			return nil, err
		}

		if reOpen {
			err = issue_dao.IssueDao.IncReOpenCount(sessCtx, req.ProjectId, req.IssueId)
			if err != nil {
				return nil, err
			}
		}
		return &project_issue_api.ChangeStateResponse{
			Code: project_issue_api.ChangeStateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	//记录事件
	go apiImpl.addChangeStateEvent(sessionItem, req, projectItem, issueItem)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: req.ProjectId,
		IssueId:   req.IssueId,
	})
	//检查是否触发reopen告警
	if reOpen {
		go apiImpl.checkReOpen(req.ProjectId, req.IssueId, uint32(issueItem.IssueType), issueItem.ReOpenCount+1)
	}
	return ret.(*project_issue_api.ChangeStateResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) UpdateProcessStage(ctx context.Context, req *project_issue_api.UpdateProcessStageRequest) (*project_issue_api.UpdateProcessStageResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, _, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	if projectItem.Closed {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}

	if issueItem.State != int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_PROCESS) {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_WRONG_STATE,
			ErrMsg: "错误的状态",
		}, nil
	}
	if issueItem.ProcessStage == int32(req.ProcessStage) {
		return &project_issue_api.UpdateProcessStageResponse{
			Code: project_issue_api.UpdateProcessStageResponse_CODE_OK,
		}, nil
	}
	if issueItem.ExecUserId != sessionItem.UserId {
		return &project_issue_api.UpdateProcessStageResponse{
			Code:   project_issue_api.UpdateProcessStageResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	nowTime := time.Now().UnixNano() / 1e6

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.UpdateState(sessCtx, req.ProjectId, req.IssueId,
			int32(project_issue_api.ISSUE_STATE_ISSUE_STATE_PROCESS), int32(req.ProcessStage), nowTime)
		if err != nil {
			return nil, err
		}
		return &project_issue_api.UpdateProcessStageResponse{
			Code: project_issue_api.UpdateProcessStageResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	//记录事件
	go apiImpl.addUpdateProcessStageEvent(sessionItem, req, projectItem, issueItem)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.UpdateIssueNotice{
		ProjectId: req.ProjectId,
		IssueId:   req.IssueId,
	})
	return ret.(*project_issue_api.UpdateProcessStageResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) List(ctx context.Context, req *project_issue_api.ListRequest) (*project_issue_api.ListResponse, error) {
	emptyList := []*project_issue_api.IssueInfo{}
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.ListResponse{
			Code:     project_issue_api.ListResponse_CODE_WRONG_SESSION,
			ErrMsg:   "会话不存在",
			InfoList: emptyList,
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.ListResponse{
			Code:     project_issue_api.ListResponse_CODE_NO_PROJECT,
			ErrMsg:   "项目不存在",
			InfoList: emptyList,
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.ListResponse{
			Code:     project_issue_api.ListResponse_CODE_NO_PERMISSION,
			ErrMsg:   "没有权限",
			InfoList: emptyList,
		}, nil
	} else if err != nil {
		return nil, err
	}

	watchIssueIdList := []string{}
	if req.ListParam.FilterByWatch {
		if (!req.ListParam.FilterByIssueType) || (req.ListParam.FilterByIssueType && req.ListParam.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
			tmpIdList, err := watch_dao.MyWatchDao.DistinctTargetId(ctx, req.ProjectId, sessionItem.UserId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK))
			if err != nil {
				return nil, err
			}
			watchIssueIdList = append(watchIssueIdList, tmpIdList...)
		}
		if (!req.ListParam.FilterByIssueType) || (req.ListParam.FilterByIssueType && req.ListParam.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			tmpIdList, err := watch_dao.MyWatchDao.DistinctTargetId(ctx, req.ProjectId, sessionItem.UserId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG))
			if err != nil {
				return nil, err
			}
			watchIssueIdList = append(watchIssueIdList, tmpIdList...)
		}
	}

	count, err := issue_dao.IssueDao.Count(ctx, req.ProjectId, req.ListParam, watchIssueIdList)
	if err != nil {
		return nil, err
	}
	issueItemList, err := issue_dao.IssueDao.List(ctx, req.ProjectId, req.ListParam, watchIssueIdList, req.SortKey, req.SortType, int64(req.Offset), int64(req.Limit))
	if err != nil {
		return nil, err
	}

	infoList, err := apiImpl.listIssue(ctx, sessionItem, projectItem, roleItem, issueItemList)
	if err != nil {
		return nil, err
	}

	return &project_issue_api.ListResponse{
		Code:       project_issue_api.ListResponse_CODE_OK,
		TotalCount: count,
		InfoList:   infoList,
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) listIssue(ctx context.Context,
	sessionItem *dao.SessionDbItem,
	projectItem *project_dao.ProjectInfoDbItem,
	roleItem *project_dao.RoleInfoDbItem,
	issueItemList []*issue_dao.IssueInfoDbItem) ([]*project_issue_api.IssueInfo, error) {
	spritIdMap := map[string]bool{}
	for _, issueItem := range issueItemList {
		if issueItem.SpritId != "" {
			spritIdMap[issueItem.SpritId] = true
		}
	}
	spritIdList := []string{}
	for sprintId := range spritIdMap {
		spritIdList = append(spritIdList, sprintId)
	}

	//获取entryItemMap
	entryItemList, err := project_entry_dao.EntryDao.ListById(ctx, spritIdList)
	if err != nil {
		return nil, err
	}
	entryItemMap := map[string]*project_entry_dao.EntryInfoDbItem{}
	for _, entryItem := range entryItemList {
		if entryItem.ProjectId != projectItem.ProjectId {
			continue
		}
		if entryItem.EntryType != uint32(project_entry_api.ENTRY_TYPE_ENTRY_TYPE_SPRIT) {
			continue
		}
		entryItemMap[entryItem.EntryId] = entryItem
	}

	//获取关注信息
	issueIdList := []string{}
	for _, issueItem := range issueItemList {
		issueIdList = append(issueIdList, issueItem.IssueId)
	}
	watchItemMap := map[string][]string{}
	watchUserIdList := []string{}
	tmpWatchItemMap, tmpWatchUserIdList, err := watch_dao.MyWatchDao.CalcWatchUserMap(ctx, projectItem.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK), issueIdList)
	if err != nil {
		return nil, err
	}
	for k, v := range tmpWatchItemMap {
		watchItemMap[k] = v
	}
	watchUserIdList = append(watchUserIdList, tmpWatchUserIdList...)
	tmpWatchItemMap, tmpWatchUserIdList, err = watch_dao.MyWatchDao.CalcWatchUserMap(ctx, projectItem.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG), issueIdList)
	if err != nil {
		return nil, err
	}
	for k, v := range tmpWatchItemMap {
		watchItemMap[k] = v
	}
	watchUserIdList = append(watchUserIdList, tmpWatchUserIdList...)

	//获取用户信息
	userIdList := []string{}
	for _, issueItem := range issueItemList {
		userIdList = append(userIdList, issueItem.CreateUserId)
		if issueItem.ExecUserId != "" {
			userIdList = append(userIdList, issueItem.ExecUserId)
		}
		if issueItem.CheckUserId != "" {
			userIdList = append(userIdList, issueItem.CheckUserId)
		}
	}
	userIdList = append(userIdList, watchUserIdList...)
	userItemList, err := user_dao.UserInfoDao.ListById(ctx, userIdList)
	if err != nil {
		return nil, err
	}
	userItemMap := map[string]*user_dao.UserInfoDbItem{}
	for _, userItem := range userItemList {
		userItemMap[userItem.UserId] = userItem
	}

	//获取子任务状态
	subIssueStatusMap, err := apiImpl.calcSubIssueStatusMap(ctx, projectItem.ProjectId, issueIdList)
	if err != nil {
		return nil, err
	}
	infoList := []*project_issue_api.IssueInfo{}
	for _, issueItem := range issueItemList {
		userPerm := apiImpl.calcUserPerm(sessionItem, projectItem, roleItem, issueItem, subIssueStatusMap[issueItem.IssueId])
		sprintName := ""
		entryItem, ok := entryItemMap[issueItem.SpritId]
		if ok {
			sprintName = entryItem.EntryTitle
		}
		info := issueItem.ToIssueInfo(sprintName, userPerm, subIssueStatusMap[issueItem.IssueId])
		infoList = append(infoList, info)
	}

	//获取项目需求信息
	linkItemList, err := requirement_dao.LinkIssueInfoDao.ListByIssueId(ctx, projectItem.ProjectId, issueIdList)
	if err != nil {
		return nil, err
	}
	requirementIdList := []string{}
	linkItemMap := map[string]*requirement_dao.LinkIssueInfoDbItem{}
	for _, linkItem := range linkItemList {
		requirementIdList = append(requirementIdList, linkItem.RequirementId)
		linkItemMap[linkItem.IssueId] = linkItem
	}
	reqTitleMap, err := requirement_dao.RequirementInfoDao.GetTitleMapById(ctx, projectItem.ProjectId, requirementIdList)
	if err != nil {
		return nil, err
	}
	//获取标签
	tagItemList, err := project_dao.TagInfoDao.List(ctx, projectItem.ProjectId, project_api.TAG_SCOPE_TYPE_TAG_SCOPE_ALL)
	if err != nil {
		return nil, err
	}
	tagItemMap := map[string]*project_dao.TagInfoDbItem{}
	for _, tagItem := range tagItemList {
		tagItemMap[tagItem.TagId] = tagItem
	}
	//生成结果
	for _, info := range infoList {
		createUser, ok := userItemMap[info.CreateUserId]
		if ok {
			info.CreateDisplayName = createUser.BasicInfo.DisplayName
		}
		execUser, ok := userItemMap[info.ExecUserId]
		if ok {
			info.ExecDisplayName = execUser.BasicInfo.DisplayName
		}
		checkUser, ok := userItemMap[info.CheckUserId]
		if ok {
			info.CheckDisplayName = checkUser.BasicInfo.DisplayName
		}
		//补充标签信息
		tagInfoList := []*project_issue_api.IssueTag{}
		for _, tagId := range info.BasicInfo.TagIdList {
			tagItem, ok := tagItemMap[tagId]
			if ok {
				if tagItem.UseInTask && info.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK {
					tagInfoList = append(tagInfoList, &project_issue_api.IssueTag{
						TagId:   tagId,
						TagName: tagItem.TagName,
						BgColor: tagItem.BgColor,
					})
				} else if tagItem.UseInBug && info.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG {
					tagInfoList = append(tagInfoList, &project_issue_api.IssueTag{
						TagId:   tagId,
						TagName: tagItem.TagName,
						BgColor: tagItem.BgColor,
					})
				}
			}
		}
		info.TagInfoList = tagInfoList
		//补充项目需求信息
		linkItem, ok := linkItemMap[info.IssueId]
		if ok {
			info.RequirementId = linkItem.RequirementId
			reqTitle, ok := reqTitleMap[linkItem.RequirementId]
			if ok {
				info.RequirementTitle = reqTitle
			}
		}
		watchUserIdList, ok := watchItemMap[info.IssueId]
		if ok {
			for _, watchUserId := range watchUserIdList {
				if watchUserId == sessionItem.UserId {
					info.MyWatch = true
				}
				userItem, ok := userItemMap[watchUserId]
				if ok {
					info.WatchUserList = append(info.WatchUserList, &project_issue_api.WatchUser{
						MemberUserId: userItem.UserId,
						DisplayName:  userItem.BasicInfo.DisplayName,
						LogoUri:      userItem.BasicInfo.LogoUri,
					})
				}
			}
		}
	}
	return infoList, nil
}

func (apiImpl *ProjectIssueApiImpl) ListId(ctx context.Context, req *project_issue_api.ListIdRequest) (*project_issue_api.ListIdResponse, error) {
	emptyList := []string{}
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.ListIdResponse{
			Code:        project_issue_api.ListIdResponse_CODE_WRONG_SESSION,
			ErrMsg:      "会话不存在",
			IssueIdList: emptyList,
		}, nil
	}
	_, _, _, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.ListIdResponse{
			Code:        project_issue_api.ListIdResponse_CODE_NO_PROJECT,
			ErrMsg:      "项目不存在",
			IssueIdList: emptyList,
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.ListIdResponse{
			Code:        project_issue_api.ListIdResponse_CODE_NO_PERMISSION,
			ErrMsg:      "没有权限",
			IssueIdList: emptyList,
		}, nil
	} else if err != nil {
		return nil, err
	}

	watchIssueIdList := []string{}
	if req.ListParam.FilterByWatch {
		if (!req.ListParam.FilterByIssueType) || (req.ListParam.FilterByIssueType && req.ListParam.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
			tmpIdList, err := watch_dao.MyWatchDao.DistinctTargetId(ctx, req.ProjectId, sessionItem.UserId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK))
			if err != nil {
				return nil, err
			}
			watchIssueIdList = append(watchIssueIdList, tmpIdList...)
		}
		if (!req.ListParam.FilterByIssueType) || (req.ListParam.FilterByIssueType && req.ListParam.IssueType == project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			tmpIdList, err := watch_dao.MyWatchDao.DistinctTargetId(ctx, req.ProjectId, sessionItem.UserId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG))
			if err != nil {
				return nil, err
			}
			watchIssueIdList = append(watchIssueIdList, tmpIdList...)
		}
	}

	issueIdList, err := issue_dao.IssueDao.ListId(ctx, req.ProjectId, req.ListParam, watchIssueIdList, req.SortKey, req.SortType, int64(req.MaxCount))
	if err != nil {
		return nil, err
	}
	return &project_issue_api.ListIdResponse{
		Code:        project_issue_api.ListIdResponse_CODE_OK,
		IssueIdList: issueIdList,
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) ListById(ctx context.Context, req *project_issue_api.ListByIdRequest) (*project_issue_api.ListByIdResponse, error) {
	emptyList := []*project_issue_api.IssueInfo{}
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.ListByIdResponse{
			Code:     project_issue_api.ListByIdResponse_CODE_WRONG_SESSION,
			ErrMsg:   "会话不存在",
			InfoList: emptyList,
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.ListByIdResponse{
			Code:     project_issue_api.ListByIdResponse_CODE_NO_PROJECT,
			ErrMsg:   "项目不存在",
			InfoList: emptyList,
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.ListByIdResponse{
			Code:     project_issue_api.ListByIdResponse_CODE_NO_PERMISSION,
			ErrMsg:   "没有权限",
			InfoList: emptyList,
		}, nil
	} else if err != nil {
		return nil, err
	}
	issueItemList, err := issue_dao.IssueDao.ListById(ctx, req.ProjectId, req.IssueIdList)
	if err != nil {
		return nil, err
	}
	infoList, err := apiImpl.listIssue(ctx, sessionItem, projectItem, roleItem, issueItemList)
	if err != nil {
		return nil, err
	}
	return &project_issue_api.ListByIdResponse{
		Code:     project_issue_api.ListByIdResponse_CODE_OK,
		InfoList: infoList,
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) GetMyTodoStatus(ctx context.Context, req *project_issue_api.GetMyTodoStatusRequest) (*project_issue_api.GetMyTodoStatusResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.GetMyTodoStatusResponse{
			Code:   project_issue_api.GetMyTodoStatusResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectIdList, err := project_dao.MemberInfoDao.ListProjectByMember(ctx, sessionItem.UserId)
	if err != nil {
		return nil, err
	}
	count, err := issue_dao.IssueDao.CountMyTodo(ctx, projectIdList, sessionItem.UserId)
	if err != nil {
		return nil, err
	}

	return &project_issue_api.GetMyTodoStatusResponse{
		Code:       project_issue_api.GetMyTodoStatusResponse_CODE_OK,
		TotalCount: count,
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) ListMyTodo(ctx context.Context, req *project_issue_api.ListMyTodoRequest) (*project_issue_api.ListMyTodoResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.ListMyTodoResponse{
			Code:   project_issue_api.ListMyTodoResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectIdList, err := project_dao.MemberInfoDao.ListProjectByMember(ctx, sessionItem.UserId)
	if err != nil {
		return nil, err
	}
	count, err := issue_dao.IssueDao.CountMyTodo(ctx, projectIdList, sessionItem.UserId)
	if err != nil {
		return nil, err
	}
	issueItemList, err := issue_dao.IssueDao.ListMyTodo(ctx, projectIdList, sessionItem.UserId, req.SortKey, req.SortType, int64(req.Offset), int64(req.Limit))
	if err != nil {
		return nil, err
	}
	if len(issueItemList) == 0 {
		return &project_issue_api.ListMyTodoResponse{
			Code:       project_issue_api.ListMyTodoResponse_CODE_OK,
			TotalCount: count,
			InfoList:   []*project_issue_api.IssueInfo{},
		}, nil
	}
	//重建projectIdList，为了较少后续数据获取
	projectIdList = []string{}
	issueIdList := []string{}
	tmpMap := map[string]bool{}
	for _, issueItem := range issueItemList {
		issueIdList = append(issueIdList, issueItem.IssueId)
		_, ok := tmpMap[issueItem.ProjectId]
		if !ok {
			tmpMap[issueItem.ProjectId] = true
			projectIdList = append(projectIdList, issueItem.ProjectId)
		}
	}
	//获取spirt信息
	tmpMap = map[string]bool{}
	spritIdList := []string{}
	for _, issueItem := range issueItemList {
		if issueItem.SpritId != "" {
			_, ok = tmpMap[issueItem.SpritId]
			if !ok {
				tmpMap[issueItem.SpritId] = true
				spritIdList = append(spritIdList, issueItem.SpritId)
			}
		}
	}
	entryItemList, err := project_entry_dao.EntryDao.ListById(ctx, spritIdList)
	if err != nil {
		return nil, err
	}
	entryItemMap := map[string]*project_entry_dao.EntryInfoDbItem{}
	for _, entryItem := range entryItemList {
		if entryItem.EntryType != uint32(project_entry_api.ENTRY_TYPE_ENTRY_TYPE_SPRIT) {
			continue
		}
		entryItemMap[entryItem.EntryId] = entryItem
	}
	//获取项目信息
	projectItemList, err := project_dao.ProjectInfoDao.ListById(ctx, projectIdList, false, false)
	if err != nil {
		return nil, err
	}
	projectItemMap := map[string]*project_dao.ProjectInfoDbItem{}
	for _, projectItem := range projectItemList {
		projectItemMap[projectItem.ProjectId] = projectItem
	}
	//获取roleInfo
	memberItemList, err := project_dao.MemberInfoDao.ListMemberByProjectAndUser(ctx, projectIdList, sessionItem.UserId)
	if err != nil {
		return nil, err
	}
	roleIdList := []string{}
	for _, memberItem := range memberItemList {
		roleIdList = append(roleIdList, memberItem.RoleId)
	}
	roleItemList, err := project_dao.MemberRoleDao.ListById(ctx, projectIdList, roleIdList)
	if err != nil {
		return nil, err
	}
	roleItemMap := map[string]*project_dao.RoleInfoDbItem{}
	for _, roleItem := range roleItemList {
		roleItemMap[roleItem.ProjectId] = roleItem
	}

	//获取子任务状态
	subIssueStatusMap, err := apiImpl.calcSubIssueStatusMap(ctx, "", issueIdList)
	if err != nil {
		return nil, err
	}

	infoList := []*project_issue_api.IssueInfo{}
	userIdList := []string{}
	for _, issueItem := range issueItemList {
		userIdList = append(userIdList, issueItem.CreateUserId)
		if issueItem.ExecUserId != "" {
			userIdList = append(userIdList, issueItem.ExecUserId)
		}
		if issueItem.CheckUserId != "" {
			userIdList = append(userIdList, issueItem.CheckUserId)
		}
		projectItem := projectItemMap[issueItem.ProjectId]
		roleItem := roleItemMap[issueItem.ProjectId]
		userPerm := apiImpl.calcUserPerm(sessionItem, projectItem, roleItem, issueItem, subIssueStatusMap[issueItem.IssueId])
		sprintName := ""
		entryItem, ok := entryItemMap[issueItem.SpritId]
		if ok {
			sprintName = entryItem.EntryTitle
		}
		info := issueItem.ToIssueInfo(sprintName, userPerm, subIssueStatusMap[issueItem.IssueId])
		//TODO 补充关注信息
		infoList = append(infoList, info)
	}
	userItemList, err := user_dao.UserInfoDao.ListById(ctx, userIdList)
	if err != nil {
		return nil, err
	}
	userItemMap := map[string]*user_dao.UserInfoDbItem{}
	for _, userItem := range userItemList {
		userItemMap[userItem.UserId] = userItem
	}
	//获取项目需求信息
	linkItemList, err := requirement_dao.LinkIssueInfoDao.ListByIssueId(ctx, "", issueIdList)
	if err != nil {
		return nil, err
	}
	requirementIdList := []string{}
	linkItemMap := map[string]*requirement_dao.LinkIssueInfoDbItem{}
	for _, linkItem := range linkItemList {
		requirementIdList = append(requirementIdList, linkItem.RequirementId)
		linkItemMap[linkItem.IssueId] = linkItem
	}
	reqTitleMap, err := requirement_dao.RequirementInfoDao.GetTitleMapById(ctx, "", requirementIdList)
	if err != nil {
		return nil, err
	}
	//add user display name
	for _, info := range infoList {
		userItem, ok := userItemMap[info.CreateUserId]
		if ok {
			info.CreateDisplayName = userItem.BasicInfo.DisplayName
		}
		userItem, ok = userItemMap[info.ExecUserId]
		if ok {
			info.ExecDisplayName = userItem.BasicInfo.DisplayName
		}
		userItem, ok = userItemMap[info.CheckUserId]
		if ok {
			info.CheckDisplayName = userItem.BasicInfo.DisplayName
		}
		//补充项目需求信息
		linkItem, ok := linkItemMap[info.IssueId]
		if ok {
			info.RequirementId = linkItem.RequirementId
			reqTitle, ok := reqTitleMap[linkItem.RequirementId]
			if ok {
				info.RequirementTitle = reqTitle
			}
		}
		//TODO 补充子任务
	}
	return &project_issue_api.ListMyTodoResponse{
		Code:       project_issue_api.ListMyTodoResponse_CODE_OK,
		TotalCount: count,
		InfoList:   infoList,
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) Get(ctx context.Context, req *project_issue_api.GetRequest) (*project_issue_api.GetResponse, error) {
	emptyIssue := &project_issue_api.IssueInfo{
		BasicInfo: &project_issue_api.BasicIssueInfo{},
		ExtraInfo: &project_issue_api.IssueInfo_ExtraTaskInfo{
			ExtraTaskInfo: &project_issue_api.ExtraTaskInfo{},
		},
		UserIssuePerm: &project_issue_api.UserIssuePerm{
			NextStateList: []project_issue_api.ISSUE_STATE{},
		},
		WatchUserList: []*project_issue_api.WatchUser{},
	}
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.GetResponse{
			Code:   project_issue_api.GetResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
			Info:   emptyIssue,
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.GetResponse{
			Code:   project_issue_api.GetResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
			Info:   emptyIssue,
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.GetResponse{
			Code:   project_issue_api.GetResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
			Info:   emptyIssue,
		}, nil
	} else if err != nil {
		return nil, err
	}
	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.GetResponse{
			Code:   project_issue_api.GetResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
			Info:   emptyIssue,
		}, nil
	}
	infoList, err := apiImpl.listIssue(ctx, sessionItem, projectItem, roleItem, []*issue_dao.IssueInfoDbItem{issueItem})
	if err != nil {
		return nil, err
	}
	if len(infoList) != 1 {
		return &project_issue_api.GetResponse{
			Code:   project_issue_api.GetResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
			Info:   emptyIssue,
		}, nil
	}

	return &project_issue_api.GetResponse{
		Code: project_issue_api.GetResponse_CODE_OK,
		Info: infoList[0],
	}, nil
}

func (apiImpl *ProjectIssueApiImpl) Remove(ctx context.Context, req *project_issue_api.RemoveRequest) (*project_issue_api.RemoveResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	projectItem, _, roleItem, err := utils.GetProjectMemberRole(ctx, req.ProjectId, sessionItem.UserId)
	if err == utils.ErrNoProject {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_NO_PROJECT,
			ErrMsg: "项目不存在",
		}, nil
	} else if err == utils.ErrNoRole {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	} else if err != nil {
		return nil, err
	}
	if projectItem.Closed {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}
	issueItem, err := issue_dao.IssueDao.Get(ctx, req.ProjectId, req.IssueId)
	if err != nil {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_NO_ISSUE,
			ErrMsg: "工单不存在",
		}, nil
	}
	if !(sessionItem.UserId == projectItem.OwnerUserId || roleItem.BasicInfo.Admin || sessionItem.UserId == issueItem.CreateUserId) {
		return &project_issue_api.RemoveResponse{
			Code:   project_issue_api.RemoveResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	//获取关联的需求
	requirementId := ""
	linkItem, err := requirement_dao.LinkIssueInfoDao.Get(ctx, req.IssueId, "")
	if err == nil {
		requirementId = linkItem.RequirementId
	}

	nowTime := time.Now().UnixNano() / 1e6

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = issue_dao.IssueDao.Remove(sessCtx, req.ProjectId, req.IssueId)
		if err != nil {
			return nil, err
		}
		err = issue_dao.IssueRecycleDao.Insert(sessCtx, issueItem)
		if err != nil {
			return nil, err
		}
		recycleItemType := project_recycle_api.RECYCLE_ITEM_TYPE_RECYCLE_ITEM_TASK
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			recycleItemType = project_recycle_api.RECYCLE_ITEM_TYPE_RECYCLE_ITEM_BUG
		}
		err = project_dao.RecycleDao.Insert(sessCtx, &project_dao.RecycleItemInfoDbItem{
			RecycleItemId:   req.IssueId,
			ProjectId:       req.ProjectId,
			RecycleItemType: uint32(recycleItemType),
			RemoveUserId:    sessionItem.UserId,
			RemoveTime:      nowTime,
		})
		if err != nil {
			return nil, err
		}

		err = issue_dao.DependDao.RemoveByIssue(sessCtx, req.ProjectId, req.IssueId)
		if err != nil {
			return nil, err
		}
		err = issue_dao.DependDao.RemoveByDependIssue(sessCtx, req.ProjectId, req.IssueId)
		if err != nil {
			return nil, err
		}
		err = project_dao.MemberStateDao.MarkIssueStateDirty(sessCtx, req.ProjectId, project_issue_api.ISSUE_TYPE(issueItem.IssueType), []string{
			issueItem.CreateUserId,
			issueItem.ExecUserId,
			issueItem.CheckUserId,
		})
		if err != nil {
			return nil, err
		}
		//处理需求链接
		if requirementId != "" {
			err = requirement_dao.LinkIssueInfoDao.Remove(sessCtx, req.IssueId, requirementId)
			if err != nil {
				return nil, err
			}
			count, err := requirement_dao.LinkIssueInfoDao.Count(sessCtx, req.ProjectId, requirementId)
			if err != nil {
				return nil, err
			}
			err = requirement_dao.RequirementInfoDao.UpdateIssueLinkCount(sessCtx, req.ProjectId, requirementId, count)
			if err != nil {
				return nil, err
			}
		}
		//删除关注和未读评论
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_TASK) {
			err = watch_dao.MyWatchDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_TASK), req.IssueId)
			if err != nil {
				return nil, err
			}
			err = comment_dao.UnReadDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_comment_api.COMMENT_TARGET_TYPE_COMMENT_TARGET_TASK), req.IssueId)
			if err != nil {
				return nil, err
			}
		}
		if issueItem.IssueType == int32(project_issue_api.ISSUE_TYPE_ISSUE_TYPE_BUG) {
			err = watch_dao.MyWatchDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_watch_api.WATCH_TARGET_TYPE_WATCH_TARGET_BUG), req.IssueId)
			if err != nil {
				return nil, err
			}
			err = comment_dao.UnReadDao.RemoveByTarget(sessCtx, req.ProjectId, uint32(project_comment_api.COMMENT_TARGET_TYPE_COMMENT_TARGET_BUG), req.IssueId)
			if err != nil {
				return nil, err
			}
		}
		return &project_issue_api.RemoveResponse{
			Code: project_issue_api.RemoveResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}

	//记录事件
	go apiImpl.addRemoveEvent(sessionItem, req, projectItem, issueItem)
	//发送通知
	go api_common.SendNoticeToProjectScope(context.Background(), req.ProjectId, &notices_issue.RemoveIssueNotice{
		ProjectId: req.ProjectId,
		IssueId:   req.IssueId,
	})
	return ret.(*project_issue_api.RemoveResponse), nil
}

func (apiImpl *ProjectIssueApiImpl) calcSubIssueStatusMap(ctx context.Context, projectId string, issueIdList []string) (map[string]*project_issue_api.SubIssueStatus, error) {
	subIssueItemList, err := issue_dao.SubIssueDao.ListByIssue(ctx, projectId, issueIdList)
	if err != nil {
		return nil, err
	}
	retMap := map[string]*project_issue_api.SubIssueStatus{}
	for _, subIssueItem := range subIssueItemList {
		oldStatus, ok := retMap[subIssueItem.IssueId]
		if !ok {
			oldStatus = &project_issue_api.SubIssueStatus{
				SubIssueList: []*project_issue_api.SimpleSubIssue{},
			}
		}
		oldStatus.SubIssueList = append(oldStatus.SubIssueList, subIssueItem.ToSimpleSubIssue())
		retMap[subIssueItem.IssueId] = oldStatus
	}
	for _, issueId := range issueIdList {
		_, ok := retMap[issueId]
		if !ok {
			retMap[issueId] = &project_issue_api.SubIssueStatus{
				SubIssueList: []*project_issue_api.SimpleSubIssue{},
			}
		}
	}
	for _, status := range retMap {
		status.TotalCount = uint32(len(status.SubIssueList))
		doneCount := 0
		for _, subIssue := range status.SubIssueList {
			if subIssue.Done {
				doneCount += 1
			}
		}
		status.DoneCount = uint32(doneCount)
	}
	return retMap, nil
}
